<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Manual Message Relay with Compression</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            display: flex;
            flex-direction: column;
            height: 100vh;
        }
        
        .container {
            display: flex;
            flex-direction: column;
            height: 100%;
        }
        
        .iframe-container {
            flex: 1;
            display: flex;
        }
        
        .iframe-half {
            flex: 1;
            position: relative;
            border-right: 2px solid #ccc;
        }
        
        .iframe-half:last-child {
            border-right: none;
        }
        
        iframe {
            width: 100%;
            height: 100%;
            border: none;
        }
        
        .controls {
            background: #f0f0f0;
            padding: 15px;
            border-top: 1px solid #ccc;
        }
        
        .control-group {
            margin-bottom: 15px;
        }
        
        .control-group:last-child {
            margin-bottom: 0;
        }
        
        label {
            display: block;
            margin-bottom: 5px;
            font-weight: bold;
        }
        
        textarea {
            width: 100%;
            height: 60px;
            font-family: monospace;
            padding: 8px;
            box-sizing: border-box;
            margin-bottom: 5px;
        }
        
        button {
            padding: 8px 15px;
            background: #4285f4;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            margin-right: 5px;
        }
        
        button:hover {
            background: #3367d6;
        }
        
        .status {
            margin-top: 5px;
            font-size: 0.9em;
            color: #666;
        }
        
        .debug-output {
            margin-top: 10px;
            border: 1px solid #ddd;
            padding: 10px;
            font-family: monospace;
            font-size: 12px;
            max-height: 950px;
            overflow-y: auto;
            background: #f9f9f9;
        }
		
		.debug-output > div {
			margin: 10px 0;
		}
        
        .connection-status {
            padding: 8px;
            margin-bottom: 10px;
            background: #eee;
            border-radius: 4px;
        }

        .compression-stats {
            margin-top: 5px;
            color: #666;
            font-size: 0.9em;
        }

        .compression-toggle {
            margin-top: 10px;
        }
        
        .transfer-buttons {
            display: flex;
            justify-content: center;
            padding: 10px 0;
            background: #e0e0e0;
        }
        
        .transfer-btn {
            background: #ff5722;
            margin: 0 10px;
        }
        
        .transfer-btn:hover {
            background: #e64a19;
        }
        
        .iframe-label {
            position: absolute;
            top: 0;
            left: 0;
            background: rgba(0,0,0,0.7);
            color: white;
            padding: 4px 8px;
            font-size: 12px;
            z-index: 10;
        }
        
        .auto-transfer {
            background: #4caf50;
        }
        
        .auto-transfer:hover {
            background: #388e3c;
        }
        
        .auto-transfer-active {
            background: #f44336;
        }
        
        .auto-transfer-active:hover {
            background: #d32f2f;
        }
    </style>
</head>
<body>
    <div class="container">
        <div class="iframe-container" id="iframe-container">
            <div class="iframe-half" id="iframe-left">
                <div class="iframe-label">Left Session</div>
                <!-- Left iframe will be added here via JS -->
            </div>
            <div class="iframe-half" id="iframe-right">
                <div class="iframe-label">Right Session</div>
                <!-- Right iframe will be added here via JS -->
            </div>
        </div>
        
        <div class="transfer-buttons">
            <button id="leftToRight" class="transfer-btn">Transfer Left → Right</button>
            <button id="autoTransfer" class="auto-transfer">Enable Auto Transfer</button>
            <button id="rightToLeft" class="transfer-btn">Transfer Right → Left</button>
        </div>
        
        <div class="controls">
            <div class="connection-status" id="connectionStatus">
                Connection Status: Waiting for connections...
            </div>
            
            <div class="control-group">
                <label for="outputArea">Outgoing messages:</label>
                <textarea id="outputArea" readonly></textarea>
                <button id="debugToggle">Toggle Debug</button>
                <span id="compressionStats" class="compression-stats"></span>
                <div id="outputStatus" class="status"></div>
                <div id="debugOutput" class="debug-output" style="display:none;"></div>
                <div class="compression-toggle">
                    <input type="checkbox" id="enableCompression" checked>
                    <label for="enableCompression" style="display:inline; font-weight:normal;">Enable Compression</label>
                </div>
            </div>
        </div>
    </div>

    <script>
        // URL Params Helper
        (function(w) {
            w.URLSearchParams = w.URLSearchParams || function(searchString) {
                var self = this;
                searchString = searchString.replace("??", "?");
                self.searchString = searchString;
                self.get = function(name) {
                    var results = new RegExp('[\?&]' + name + '=([^&#]*)').exec(self.searchString);
                    if (results == null) {
                        return null;
                    } else {
                        return decodeURI(results[1]) || 0;
                    }
                };
            };
        })(window);

function compressIP(ip) {
	if (ip === '127.0.0.1') return 'L';
	if (ip === '0.0.0.0') return 'Z';
	if (ip.startsWith('192.168.')) return 'P' + ip.split('.').slice(2).join('');
	if (ip.startsWith('10.')) return 'T' + ip.split('.').slice(1).join('');
	if (ip.startsWith('172.')) return 'S' + ip.split('.').slice(1).join('');
	const parts = ip.split('.');
	let num = 0;
	for (let i = 0; i < 4; i++) {
		num = num * 256 + parseInt(parts[i]);
	}
	return num.toString(36).toUpperCase();
}

function decompressIP(compressed) {
	if (compressed === 'L') return '127.0.0.1';
	if (compressed === 'Z') return '0.0.0.0';
	if (compressed.startsWith('P')) {
		const suffix = compressed.substring(1);
		if (suffix.length === 0) return '192.168.0.0';
		if (suffix.length === 1) return `192.168.${suffix}.0`;
		return `192.168.${suffix.substring(0, 1)}.${suffix.substring(1)}`;
	}
	if (compressed.startsWith('T')) {
		const suffix = compressed.substring(1);
		if (suffix.length === 0) return '10.0.0.0';
		if (suffix.length === 1) return `10.${suffix}.0.0`;
		if (suffix.length === 2) return `10.${suffix.substring(0, 1)}.${suffix.substring(1)}.0`;
		return `10.${suffix.substring(0, 1)}.${suffix.substring(1, 2)}.${suffix.substring(2)}`;
	}
	if (compressed.startsWith('S')) {
		const suffix = compressed.substring(1);
		if (suffix.length === 0) return '172.0.0.0';
		if (suffix.length === 1) return `172.${suffix}.0.0`;
		if (suffix.length === 2) return `172.${suffix.substring(0, 1)}.${suffix.substring(1)}.0`;
		return `172.${suffix.substring(0, 1)}.${suffix.substring(1, 2)}.${suffix.substring(2)}`;
	}
	const num = parseInt(compressed, 36);
	return [
		(num >> 24) & 0xFF,
		(num >> 16) & 0xFF,
		(num >> 8) & 0xFF,
		num & 0xFF
	].join('.');
}

function priorityToCompact(priority) {
	return priority.toString(36);
}

function compactToPriority(compact) {
	return parseInt(compact, 36);
}

function hexToCompact(hex) {
	const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
	let result = '';
	for (let i = 0; i < hex.length; i += 6) {
		const chunk = hex.substr(i, 6);
		if (chunk.length < 6) {
			const value = parseInt(chunk, 16);
			result += chars[value % 64];
			if (chunk.length > 2) {
				result += chars[Math.floor(value / 64) % 64];
			}
		} else {
			const value = parseInt(chunk, 16);
			result += chars[value & 0x3F];
			result += chars[(value >> 6) & 0x3F];
			result += chars[(value >> 12) & 0x3F];
			result += chars[(value >> 18) & 0x3F];
		}
	}
	return result;
}

function compactToHex(compact) {
	const chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz-_";
	let result = '';
	for (let i = 0; i < compact.length; i += 4) {
		let value = 0;
		for (let j = 0; j < 4 && i + j < compact.length; j++) {
			const char = compact[i + j];
			const charValue = chars.indexOf(char);
			if (charValue === -1) {
				throw new Error(`Invalid character in compact string: ${char}`);
			}
			value |= charValue << (j * 6);
		}
		const hex = value.toString(16).padStart(6, '0');
		result += hex;
	}
	// Ensure result is exactly 64 characters (32 bytes)
	return result.substring(0, 64);
}

function compressSDPForQR(sdp) {
    const iceUfrag = sdp.match(/a=ice-ufrag:([^\r\n]+)/)[1];
    const icePwd = sdp.match(/a=ice-pwd:([^\r\n]+)/)[1];
    const fingerprintMatch = sdp.match(/a=fingerprint:sha-256 ([^\r\n]+)/);
    const fingerprint = fingerprintMatch[1].replace(/:/g, '');
    const isOffer = sdp.match(/a=setup:actpass/) !== null;
    const candidates = [];
    const candidateRegex = /a=candidate:([^\r\n]+)/g;
    let candidateMatch;
    while ((candidateMatch = candidateRegex.exec(sdp)) !== null) {
        const parts = candidateMatch[1].split(' ');
        if (parts[4].includes(':')) continue;
        const foundation = parts[0];
        const component = parts[1];
        const protocol = parts[2].toLowerCase() === 'udp' ? '1' : '2';
        const priority = parseInt(parts[3]);
        const ip = compressIP(parts[4]);
        const port = parseInt(parts[5]);
        let typeCode;
        switch(parts[7]) {
            case 'host': typeCode = '1'; break;
            case 'srflx': typeCode = '2'; break;
            case 'relay': typeCode = '3'; break;
            default: typeCode = '0';
        }
        let relInfo = '';
        const rAddrIndex = parts.indexOf('raddr');
        const rPortIndex = parts.indexOf('rport');
        if (rAddrIndex !== -1 && rPortIndex !== -1 &&
            rAddrIndex + 1 < parts.length && rPortIndex + 1 < parts.length) {
            relInfo = compressIP(parts[rAddrIndex + 1]) + parts[rPortIndex + 1];
        }
        candidates.push(`${foundation}${component}${protocol}${compressNumber(priority)}${ip}${port}${typeCode}${relInfo}`);
    }
    const qrFingerprint = fingerprintToQR(fingerprint);
    let result = `Q${isOffer ? '1' : '0'}${iceUfrag}~${icePwd}~${qrFingerprint}`;
    if (candidates.length > 0) {
        result += `~${candidates.join(',')}`;
    }
    return result;
}

function decompressSDPFromQR(compressed) {
    if (!compressed.startsWith('Q')) {
        throw new Error('Unsupported QR compression format');
    }
    const isOffer = compressed[1] === '1';
    const parts = compressed.substring(2).split('~');
    const iceUfrag = parts[0];
    const icePwd = parts[1];
    const fingerprint = qrToFingerprint(parts[2]);
    const formattedFingerprint = fingerprint.match(/.{2}/g).join(':');
    let sdp = [
        'v=0',
        'o=- ' + Math.floor(Date.now()) + ' 2 IN IP4 127.0.0.1',
        's=-',
        't=0 0',
        'a=group:BUNDLE 0',
        'a=extmap-allow-mixed',
        'a=msid-semantic: WMS',
        'm=application 9 UDP/DTLS/SCTP webrtc-datachannel',
        'c=IN IP4 0.0.0.0',
        `a=ice-ufrag:${iceUfrag}`,
        `a=ice-pwd:${icePwd}`,
        'a=ice-options:trickle',
        `a=fingerprint:sha-256 ${formattedFingerprint}`,
        `a=setup:${isOffer ? 'actpass' : 'active'}`,
        'a=mid:0'
    ].join('\r\n');
    if (parts.length > 3 && parts[3]) {
        const candidates = parts[3].split(',');
        let candidateLines = '';
        for (const candidate of candidates) {
            let i = 0;
            let j = i;
            while (j < candidate.length && /^\d$/.test(candidate[j])) j++;
            const foundation = candidate.substring(i, j);
            i = j;
            if (i >= candidate.length) continue;
            const component = candidate.substring(i, i + 1);
            i += 1;
            if (i >= candidate.length) continue;
            const protocolCode = candidate.substring(i, i + 1);
            const protocol = protocolCode === '1' ? 'UDP' : 'TCP';
            i += 1;
            if (i >= candidate.length) continue;
            j = i;
            while (j < candidate.length && /^[0-9A-Z]$/.test(candidate[j])) j++;
            const priorityEncoded = candidate.substring(i, j);
            const priority = decompressNumber(priorityEncoded);
            i = j;
            if (i >= candidate.length) continue;
            j = i;
            while (j < candidate.length && !/^\d$/.test(candidate[j]) && candidate[j] !== '1' &&
                    candidate[j] !== '2' && candidate[j] !== '3' && candidate[j] !== '0') j++;
            const ipEncoded = candidate.substring(i, j);
            const ip = decompressIP(ipEncoded);
            i = j;
            if (i >= candidate.length) continue;
            j = i;
            while (j < candidate.length && /^\d$/.test(candidate[j])) j++;
            const port = candidate.substring(i, j);
            i = j;
            if (i >= candidate.length) continue;
            const typeCode = candidate.substring(i, i + 1);
            i += 1;
            let type;
            switch(typeCode) {
                case '1': type = 'host'; break;
                case '2': type = 'srflx'; break;
                case '3': type = 'relay'; break;
                default: type = 'unknown';
            }
            candidateLines += `\r\na=candidate:${foundation} ${component} ${protocol} ${priority} ${ip} ${port} typ ${type}`;
            if (i < candidate.length && (type === 'srflx' || type === 'relay')) {
                j = i;
                while (j < candidate.length && !/^\d$/.test(candidate[j])) j++;
                const relatedIPEncoded = candidate.substring(i, j);
                const relatedIP = decompressIP(relatedIPEncoded);
                i = j;
                j = i;
                while (j < candidate.length && /^\d$/.test(candidate[j])) j++;
                const relatedPort = candidate.substring(i, j);
                if (relatedIP && relatedPort) {
                    candidateLines += ` raddr ${relatedIP} rport ${relatedPort}`;
                }
            }
            if (protocol === 'TCP') {
                candidateLines += ' tcptype active';
            }
        }
        sdp += candidateLines;
        if (candidateLines) {
            sdp += '\r\na=end-of-candidates';
        }
    }
    return sdp;
}

function compressNumber(num) {
	// Convert to base36 for compactness, prefer uppercase for QR
	return num.toString(36).toUpperCase();
}

function decompressNumber(compressed) {
	return parseInt(compressed, 36);
}

function fingerprintToQR(hex) {
    let result = '';
    for (let i = 0; i < hex.length; i += 4) {
        const chunk = hex.substr(i, 4);
        if (chunk.length < 4) {
            const value = parseInt(chunk, 16);
            result += value.toString(36).toUpperCase().padStart(3, '0').substring(0, 3);
        } else {
            const value = parseInt(chunk, 16);
            result += value.toString(36).toUpperCase().padStart(3, '0').substring(0, 3);
        }
    }
    return result;
}

function qrToFingerprint(qrFp) {
    let result = '';
    for (let i = 0; i < qrFp.length; i += 3) {
        const chunk = qrFp.substr(i, 3);
        const value = parseInt(chunk, 36);
        result += value.toString(16).padStart(4, '0').substring(0, 4);
    }
    return result.padEnd(64, '0');
}
function compressSDP(sdp) {
    try {
        // Extract key fields from SDP
        const iceUfragMatch = sdp.match(/a=ice-ufrag:([^\r\n]+)/);
        const icePwdMatch = sdp.match(/a=ice-pwd:([^\r\n]+)/);
        const fingerprintMatch = sdp.match(/a=fingerprint:sha-256 ([^\r\n]+)/);
        
        if (!iceUfragMatch || !icePwdMatch || !fingerprintMatch) {
            updateDebugOutput("SDP missing required fields");
            throw new Error("SDP missing required fields");
        }
        
        const iceUfrag = iceUfragMatch[1];
        const icePwd = icePwdMatch[1];
        const fingerprint = fingerprintMatch[1].replace(/:/g, '');
        const isOffer = sdp.match(/a=setup:actpass/) !== null;
        
        // Extract host and srflx candidates only (UDP IPv4 only)
        const candidateRegex = /a=candidate:([^\r\n]+)/g;
        let candidateMatch;
        let hostCandidates = [];
        let srflxCandidates = [];
        
        while ((candidateMatch = candidateRegex.exec(sdp)) !== null) {
            const parts = candidateMatch[1].split(' ');
            
            // Skip non-UDP, IPv6, mDNS, TCP and relay candidates
            if (parts[2].toLowerCase() !== 'udp' || 
                parts[4].includes(':') || 
                parts[4].includes('.local') ||
                parts[7] === 'relay') {
                continue;
            }
            
            // Create compact representation of candidate
            const foundation = parts[0];
            const component = parts[1];
            const priority = priorityToCompact(parseInt(parts[3]));
            const ip = compressIP(parts[4]);
            const port = parts[5];
            const type = parts[7];
            
            // Format: foundation|component|priority|ip|port
            let candidateStr = `${foundation}|${component}|${priority}|${ip}|${port}`;
            
            // Add related address info for srflx candidates
            if (type === 'srflx') {
                const rAddrIndex = parts.indexOf('raddr');
                const rPortIndex = parts.indexOf('rport');
                
                if (rAddrIndex !== -1 && rPortIndex !== -1 &&
                    rAddrIndex + 1 < parts.length && rPortIndex + 1 < parts.length) {
                    const relatedIP = compressIP(parts[rAddrIndex + 1]);
                    candidateStr += `|${relatedIP}|${parts[rPortIndex + 1]}`;
                }
                
                srflxCandidates.push(candidateStr);
            } else if (type === 'host') {
                hostCandidates.push(candidateStr);
            }
        }
        
        // Compress fingerprint using hexToCompact
        const compactFingerprint = hexToCompact(fingerprint);
        
        // Create string format: C2|o/a|ufrag|pwd|fingerprint|host1^host2^...|srflx1^srflx2^...
        let result = `C2|${isOffer ? 'o' : 'a'}|${iceUfrag}|${icePwd}|${compactFingerprint}`;
        
        // Add host candidates
        result += '|' + hostCandidates.join('^');
        
        // Add srflx candidates
        result += '|' + srflxCandidates.join('^');
        
        return result;
    } catch (e) {
        console.error("Error in compressSDP:", e);
        updateDebugOutput(`SDP compression error: ${e.message}`);
        throw e;
    }
}

function decompressSDP(compressed) {
    try {
        if (typeof compressed !== 'string') {
            compressed = JSON.stringify(compressed);
        }
        
        // Handle old format
        if (compressed.startsWith('{') && compressed.endsWith('}')) {
            try {
                const jsonData = JSON.parse(compressed);
                // This is our old JSON format, call the JSON handler
                return decompressJSONSDP(jsonData);
            } catch (e) {
                // Not valid JSON, continue with string parsing
                updateDebugOutput(`Not valid JSON format: ${e.message}`);
            }
        }
        
        // Handle old C1 format
        if (compressed.startsWith('C1|')) {
            return decompressOldSDP(compressed);
        }
        
        // Parse the new C2 string format
        // C2|o/a|ufrag|pwd|fingerprint|host1^host2^...|srflx1^srflx2^...
        const parts = compressed.split('|');
        
        if (parts[0] !== 'C2' || parts.length < 6) {
            throw new Error('Invalid compressed SDP format');
        }
        
        const isOffer = parts[1] === 'o';
        const iceUfrag = parts[2];
        const icePwd = parts[3];
        
        // Decompress fingerprint if it doesn't contain colons
        let fingerprint = parts[4];
        if (fingerprint.indexOf(':') === -1) {
            if (fingerprint.length < 32) {
                // This is likely a compact fingerprint, convert back to hex
                fingerprint = compactToHex(fingerprint);
            }
            // Format with colons
            fingerprint = fingerprint.match(/.{2}/g).join(':').toUpperCase();
        }
        
        // Build the basic SDP structure
        let sdpLines = [
            'v=0',
            'o=- ' + Math.floor(Date.now()) + ' 2 IN IP4 127.0.0.1',
            's=-',
            't=0 0',
            'a=group:BUNDLE 0',
            'a=extmap-allow-mixed',
            'a=msid-semantic: WMS',
            'm=application 9 UDP/DTLS/SCTP webrtc-datachannel',
            'c=IN IP4 0.0.0.0',
            `a=ice-ufrag:${iceUfrag}`,
            `a=ice-pwd:${icePwd}`,
            'a=ice-options:trickle',
            `a=fingerprint:sha-256 ${fingerprint}`,
            `a=setup:${isOffer ? 'actpass' : 'active'}`,
            'a=mid:0',
            'a=sctp-port:5000',
            'a=max-message-size:262144'
        ];
        
        // Add host candidates
        if (parts[5] && parts[5] !== '') {
            const hostCandidates = parts[5].split('^');
            for (const candidateStr of hostCandidates) {
                if (!candidateStr) continue;
                
                const candParts = candidateStr.split('|');
                if (candParts.length < 5) continue;
                
                const foundation = candParts[0];
                const component = candParts[1];
                const priority = compactToPriority(candParts[2]).toString();
                const ip = decompressIP(candParts[3]);
                const port = candParts[4];
                
                sdpLines.push(`a=candidate:${foundation} ${component} udp ${priority} ${ip} ${port} typ host generation 0`);
            }
        }
        
        // Add srflx candidates
        if (parts[6] && parts[6] !== '') {
            const srflxCandidates = parts[6].split('^');
            for (const candidateStr of srflxCandidates) {
                if (!candidateStr) continue;
                
                const candParts = candidateStr.split('|');
                if (candParts.length < 5) continue;
                
                const foundation = candParts[0];
                const component = candParts[1];
                const priority = compactToPriority(candParts[2]).toString();
                const ip = decompressIP(candParts[3]);
                const port = candParts[4];
                
                let candLine = `a=candidate:${foundation} ${component} udp ${priority} ${ip} ${port} typ srflx`;
                
                // Add raddr/rport if available
                if (candParts.length >= 7) {
                    const relatedIP = decompressIP(candParts[5]);
                    candLine += ` raddr ${relatedIP} rport ${candParts[6]}`;
                }
                
                candLine += ' generation 0';
                sdpLines.push(candLine);
            }
        }
        
        // Join with proper line endings
        const result = sdpLines.join('\r\n') + '\r\n';
        return result;
    } catch (e) {
        console.error("Error in decompressSDP:", e);
        updateDebugOutput(`SDP decompression error: ${e.message}`);
        throw e;
    }
}
// Fallback handler for old C1 format
function decompressOldSDP(compressed) {
    try {
        const parts = compressed.split('|');
        
        const isOffer = parts[1] === 'o';
        const iceUfrag = parts[2];
        const icePwd = parts[3];
        let fingerprint = parts[4];
        
        if (fingerprint.indexOf(':') === -1) {
            fingerprint = fingerprint.match(/.{2}/g).join(':').toUpperCase();
        }
        
        let sdpLines = [
            'v=0',
            'o=- ' + Math.floor(Date.now()) + ' 2 IN IP4 127.0.0.1',
            's=-',
            't=0 0',
            'a=group:BUNDLE 0',
            'a=extmap-allow-mixed',
            'a=msid-semantic: WMS',
            'm=application 9 UDP/DTLS/SCTP webrtc-datachannel',
            'c=IN IP4 0.0.0.0',
            `a=ice-ufrag:${iceUfrag}`,
            `a=ice-pwd:${icePwd}`,
            'a=ice-options:trickle',
            `a=fingerprint:sha-256 ${fingerprint}`,
            `a=setup:${isOffer ? 'actpass' : 'active'}`,
            'a=mid:0',
            'a=sctp-port:5000',
            'a=max-message-size:262144'
        ];
        
        return sdpLines.join('\r\n') + '\r\n';
    } catch (e) {
        console.error("Error in decompressOldSDP:", e);
        updateDebugOutput(`Old SDP decompression error: ${e.message}`);
        throw e;
    }
}

// Fallback handler for JSON format
function decompressJSONSDP(jsonData) {
    try {
        const isOffer = jsonData.t === 'o';
        const iceUfrag = jsonData.u;
        const icePwd = jsonData.p;
        
        let fingerprint = jsonData.f;
        if (fingerprint.indexOf(':') === -1) {
            fingerprint = fingerprint.match(/.{2}/g).join(':').toUpperCase();
        }
        
        let sdpLines = [
            'v=0',
            'o=- ' + Math.floor(Date.now()) + ' 2 IN IP4 127.0.0.1',
            's=-',
            't=0 0',
            'a=group:BUNDLE 0',
            'a=extmap-allow-mixed',
            'a=msid-semantic: WMS',
            'm=application 9 UDP/DTLS/SCTP webrtc-datachannel',
            'c=IN IP4 0.0.0.0',
            `a=ice-ufrag:${iceUfrag}`,
            `a=ice-pwd:${icePwd}`,
            'a=ice-options:trickle',
            `a=fingerprint:sha-256 ${fingerprint}`,
            `a=setup:${isOffer ? 'actpass' : 'active'}`,
            'a=mid:0',
            'a=sctp-port:5000',
            'a=max-message-size:262144'
        ];
        
        // Add candidates if they exist
        if (jsonData.c && Array.isArray(jsonData.c)) {
            sdpLines = sdpLines.concat(jsonData.c);
        }
        // Add host candidates from h array if it exists
        else if (jsonData.h && Array.isArray(jsonData.h)) {
            for (const c of jsonData.h) {
                if (typeof c === 'string') {
                    const parts = c.split(',');
                    if (parts.length >= 5) {
                        const priority = compactToPriority(parts[2]).toString();
                        sdpLines.push(`a=candidate:${parts[0]} ${parts[1]} udp ${priority} ${parts[3]} ${parts[4]} typ host generation 0`);
                    }
                } else if (typeof c === 'object') {
                    sdpLines.push(`a=candidate:${c.f} ${c.c} udp ${c.p} ${c.i} ${c.o} typ host generation 0`);
                }
            }
            
            // Add srflx candidates from s array if it exists
            if (jsonData.s && Array.isArray(jsonData.s)) {
                for (const c of jsonData.s) {
                    if (typeof c === 'string') {
                        const parts = c.split(',');
                        if (parts.length >= 5) {
                            const priority = compactToPriority(parts[2]).toString();
                            let line = `a=candidate:${parts[0]} ${parts[1]} udp ${priority} ${parts[3]} ${parts[4]} typ srflx`;
                            if (parts.length >= 7) {
                                line += ` raddr ${parts[5]} rport ${parts[6]}`;
                            }
                            line += ' generation 0';
                            sdpLines.push(line);
                        }
                    } else if (typeof c === 'object') {
                        let line = `a=candidate:${c.f} ${c.c} udp ${c.p} ${c.i} ${c.o} typ srflx`;
                        if (c.r && c.s) {
                            line += ` raddr ${c.r} rport ${c.s}`;
                        }
                        line += ' generation 0';
                        sdpLines.push(line);
                    }
                }
            }
        }
        
        return sdpLines.join('\r\n') + '\r\n';
    } catch (e) {
        console.error("Error in decompressJSONSDP:", e);
        updateDebugOutput(`JSON SDP decompression error: ${e.message}`);
        throw e;
    }
}

// Compress ICE candidates to a more compact string format
function compressICECandidates(candidates) {
    try {
        if (!candidates || !candidates.length) return "";
        
        // Filter for valid candidates - only UDP IPv4 host and srflx
        const validCandidates = candidates.filter(c => {
            if (!c || !c.candidate) return false;
            
            const parts = c.candidate.split(' ');
            return parts.length >= 8 && 
                   parts[2].toLowerCase() === 'udp' && 
                   !parts[4].includes(':') && 
                   !parts[4].includes('.local') &&
                   (parts[7] === 'host' || parts[7] === 'srflx');
        });
        
        if (!validCandidates.length) return "";
        
        // Separate host and srflx candidates
        const hostCandidates = [];
        const srflxCandidates = [];
        
        validCandidates.forEach(c => {
            const parts = c.candidate.split(' ');
            const foundation = parts[0].replace('candidate:', '');
            const component = parts[1];
            const priority = priorityToCompact(parseInt(parts[3]));
            const ip = parts[4];
            const port = parts[5];
            const type = parts[7];
            
            // Format: foundation,component,priority,ip,port
            let candidateStr = `${foundation},${component},${priority},${ip},${port}`;
            
            // Add related address info for srflx candidates
            if (type === 'srflx') {
                const rAddrIndex = parts.indexOf('raddr');
                const rPortIndex = parts.indexOf('rport');
                
                if (rAddrIndex !== -1 && rPortIndex !== -1 &&
                    rAddrIndex + 1 < parts.length && rPortIndex + 1 < parts.length) {
                    candidateStr += `,${parts[rAddrIndex + 1]},${parts[rPortIndex + 1]}`;
                }
                
                srflxCandidates.push(candidateStr);
            } else if (type === 'host') {
                hostCandidates.push(candidateStr);
            }
        });
        
        // Format: 'h:' + hosts joined by ^ + '|s:' + srflx joined by ^
        let result = '';
        
        if (hostCandidates.length > 0) {
            result += 'h:' + hostCandidates.join('^');
        }
        
        if (srflxCandidates.length > 0) {
            if (result) result += '|';
            result += 's:' + srflxCandidates.join('^');
        }
        
        return result;
    } catch (e) {
        console.error("Error compressing ICE candidates:", e);
        updateDebugOutput(`ICE compression error: ${e.message}`);
        return "";
    }
}

// Decompress ICE candidates from compact string format
function decompressICECandidates(compressed) {
    try {
        if (!compressed) return [];
        
        const candidates = [];
        
        // Handle the old format for backward compatibility
        if (compressed.includes(',') && !compressed.includes('h:') && !compressed.includes('s:')) {
            return decompressOldICEFormat(compressed);
        }
        
        const sections = compressed.split('|');
        
        for (const section of sections) {
            if (!section) continue;
            
            let type, candidateStrs;
            
            if (section.startsWith('h:')) {
                type = 'host';
                candidateStrs = section.substring(2).split('^');
            } else if (section.startsWith('s:')) {
                type = 'srflx';
                candidateStrs = section.substring(2).split('^');
            } else {
                // Unexpected format, skip this section
                updateDebugOutput(`Unexpected section format: ${section}`);
                continue;
            }
            
            for (const candidateStr of candidateStrs) {
                if (!candidateStr) continue;
                
                const parts = candidateStr.split(',');
                if (parts.length < 5) continue;
                
                const foundation = parts[0];
                const component = parts[1];
                const priority = compactToPriority(parts[2]);
                const ip = parts[3];
                const port = parts[4];
                
                let candidateLine = `candidate:${foundation} ${component} UDP ${priority} ${ip} ${port} typ ${type}`;
                
                // Add raddr/rport for srflx if available
                if (type === 'srflx' && parts.length >= 7) {
                    candidateLine += ` raddr ${parts[5]} rport ${parts[6]}`;
                }
                
                // Add generation
                candidateLine += ' generation 0';
                
                candidates.push({
                    candidate: candidateLine,
                    sdpMid: "0",
                    sdpMLineIndex: 0
                });
            }
        }
        
        return candidates;
    } catch (e) {
        console.error("Error decompressing ICE candidates:", e);
        updateDebugOutput(`ICE decompression error: ${e.message}`);
        return [];
    }
}

// Handle the old format for backward compatibility
function decompressOldICEFormat(compressed) {
    try {
        const candidates = [];
        
        const oldCandidates = compressed.split('/');
        for (const c of oldCandidates) {
            const parts = c.split(',');
            if (parts.length < 6) {
                updateDebugOutput(`Skipping incomplete candidate data: ${c}`);
                continue;
            }
            
            const foundation = parts[0];
            const component = parts[1];
            const protocol = parts[2] === 'u' ? 'UDP' : 'TCP';
            const priority = compactToPriority(parts[3]);
            const ip = parts[4];
            const port = parts[5];
            const typeCode = parts[6];
            
            let type = 'host';
            switch(typeCode) {
                case 'h': type = 'host'; break;
                case 's': type = 'srflx'; break;
                case 'r': type = 'relay'; break;
                default: type = 'host';
            }
            
            // Skip relay candidates - we don't want them
            if (type === 'relay') continue;
            
            // Build candidate line
            let candidateLine = `candidate:${foundation} ${component} ${protocol} ${priority} ${ip} ${port} typ ${type}`;
            
            // Add raddr/rport if available for srflx candidates
            if (type === 'srflx' && parts.length >= 9) {
                candidateLine += ` raddr ${parts[7]} rport ${parts[8]}`;
            }
            
            // Add generation 
            candidateLine += ' generation 0';
            
            candidates.push({
                candidate: candidateLine,
                sdpMid: "0",
                sdpMLineIndex: 0
            });
        }
        
        return candidates;
    } catch (e) {
        console.error("Error decompressing old ICE format:", e);
        updateDebugOutput(`Old ICE format decompression error: ${e.message}`);
        return [];
    }
}

// Update compressMessage to handle the new compression format
function compressMessage(message) {
    try {
        // Handle either string or object input
        let parsedMsg;
        if (typeof message === 'string') {
            parsedMsg = JSON.parse(message);
        } else {
            parsedMsg = message;
        }
        
        // For SDP offers/answers with descriptions
        if (parsedMsg.description && parsedMsg.description.sdp) {
            // Extract key fields
            const { UUID, from, session } = parsedMsg;
            if (from) {
                myUUID = from;
            }
            
            // Compress SDP with candidates preserved
            return JSON.stringify({
                type: "O",
                ses: session || null,
                sdp: compressSDP(parsedMsg.description.sdp)
            });
        }
        
        // For ICE candidates
        else if (parsedMsg.candidates && parsedMsg.candidates.length) {
			
			return null;
			
            // Extract key fields
            const { UUID, type, from, session } = parsedMsg;
            if (from) {
                myUUID = from;
            }
            
            // Compress candidates - only keep UDP IPv4 host and srflx candidates
            const validCandidates = parsedMsg.candidates.filter(c => {
                if (!c || !c.candidate) return false;
                
                const parts = c.candidate.split(' ');
                return parts.length >= 8 && 
                       parts[2].toLowerCase() === 'udp' && 
                       !parts[4].includes(':') && 
                       !parts[4].includes('.local') &&
                       (parts[7] === 'host' || parts[7] === 'srflx');
            });
            
            // Skip empty candidate sets
            if (!validCandidates.length) {
                return null; // Skip this message completely
            }
            
            // Create compact message
            return JSON.stringify({
                type: "C",
                ses: session || null,
                cand: compressICECandidates(validCandidates)
            });
        }
        
        // For basic messages (join, play, etc.)
        else if (parsedMsg.request) {
            // Extract key fields and create compact message
            const { request, from } = parsedMsg;
            if (from) {
                myUUID = from;
            }
            return JSON.stringify({
                type: "B",
                req: request || null
            });
        }
        
        // If we don't recognize the format, return the original message
        updateDebugOutput("Unknown message format, using original");
        return typeof message === 'string' ? message : JSON.stringify(message);
    } catch (e) {
        console.error("Compression error:", e);
        updateDebugOutput(`Message compression error: ${e.message}`);
        return typeof message === 'string' ? message : JSON.stringify(message);
    }
}

// Update decompressMessage to handle the new format
function decompressMessage(message) {
    try {
        // Parse the message first
        let parsedMsg;
        if (typeof message === 'string') {
            parsedMsg = JSON.parse(message);
        } else {
            parsedMsg = message;
        }
        
        // Check if it's our compressed format
        if (parsedMsg.type) {
            // SDP offer/answer message
            if (parsedMsg.type === "O" && parsedMsg.sdp) {
                try {
                    // Determine if it's an offer or answer based on first character of SDP
                    let isOffer = false;
                    const sdpData = parsedMsg.sdp;
                    
                    if (typeof sdpData === 'string') {
                        if (sdpData.startsWith('C1|')) {
                            isOffer = sdpData.split('|')[1] === 'o';
                        } else if (sdpData.startsWith('C2|')) {
                            isOffer = sdpData.split('|')[1] === 'o';
                        } else if (sdpData.startsWith('{') && sdpData.endsWith('}')) {
                            try {
                                const sdpObj = JSON.parse(sdpData);
                                isOffer = sdpObj.t === 'o';
                            } catch (e) {
                                updateDebugOutput(`Error parsing SDP JSON: ${e.message}`);
                            }
                        }
                    } else if (typeof sdpData === 'object') {
                        isOffer = sdpData.t === 'o';
                    }
                    
                    // Build original format
                    const result = {
                        UUID: myUUID,
                        streamID: "local",
                        description: {
                            type: isOffer ? "offer" : "answer",
                            sdp: decompressSDP(parsedMsg.sdp)
                        },
                        from: "remote",
                        roomid: "default"
                    };
                    
                    if (parsedMsg.ses) result.session = parsedMsg.ses;
                    
                    return JSON.stringify(result);
                } catch (e) {
                    console.error("Error decompressing SDP:", e);
                    updateDebugOutput(`SDP decompression failed: ${e.message}`);
                    throw e;
                }
            }
            // ICE candidates message - no changes needed
            else if (parsedMsg.type === "C") {
                try {
                    // Decompress candidates
                    const candidates = decompressICECandidates(parsedMsg.cand || "");
                    
                    // Skip if no valid candidates
                    if (!candidates || candidates.length === 0) {
                        updateDebugOutput("No valid candidates after decompression, skipping");
                        return null;
                    }
                    
                    // Build original format
                    const result = {
                        UUID: myUUID,
                        candidates: candidates,
                        from: "remote",
                        roomid: "default",
                        type: "remote"
                    };
                    
                    if (parsedMsg.ses) result.session = parsedMsg.ses;
                    
                    return JSON.stringify(result);
                } catch (e) {
                    console.error("Error decompressing candidates:", e);
                    updateDebugOutput(`Candidate decompression failed: ${e.message}`);
                    throw e;
                }
            }
            // Basic control message - no changes needed
            else if (parsedMsg.type === "B") {
                // Build original format
                const result = {
                    request: parsedMsg.req,
                    streamID: "remote",
                    from: "remote",
                    roomid: "default"
                };
                
                return JSON.stringify(result);
            }
        }
        
        // If it's not our compressed format, return it unchanged
        return typeof message === 'string' ? message : JSON.stringify(message);
    } catch (e) {
        console.error("Decompression error:", e);
        updateDebugOutput(`Message decompression error: ${e.message}`);
        // Return original in case of error
        return typeof message === 'string' ? message : JSON.stringify(message);
    }
}

        // ====================== Application Code ======================
        // Initialize variables
		var myUUID = "123";
// Initialize variables
        let messageQueues = { left: [], right: [] };
        let connectionStates = { left: {}, right: {} };
        let currentConnectionIds = { left: null, right: null };
        let processingTimeouts = { left: null, right: null };
        let iframes = { left: null, right: null };
        let roomid = "default";
        let debugMode = false;
        let compressionEnabled = true;
        let autoTransferEnabled = false;
        let messageBuffers = { left: [], right: [] };
        
        // Parse URL parameters
        var urlEdited = window.location.search.replace(/\?\?/g, "?");
        urlEdited = urlEdited.replace(/\?/g, "&");
        urlEdited = urlEdited.replace(/\&/, "?");

        if (urlEdited !== window.location.search) {
            console.warn(window.location.search + " changed to " + urlEdited);
            window.history.pushState({path: urlEdited.toString()}, '', urlEdited.toString());
        }
        
        var urlParams = new URLSearchParams(urlEdited);
        
        // Get room ID
        if (urlParams.has("room")) {
            roomid = urlParams.get("room");
        }

        // Helper functions
        function updateDebugOutput(message) {
            if (!debugMode) return;
            
            const debugEl = document.getElementById("debugOutput");
            const timestamp = new Date().toLocaleTimeString();
            debugEl.innerHTML += `<div>${timestamp}: ${message}</div>`;
            debugEl.scrollTop = debugEl.scrollHeight;
        }
        
        function updateConnectionStatus() {
            const statusEl = document.getElementById("connectionStatus");
            
            const leftActive = Object.values(connectionStates.left).filter(s => s !== "completed" && s !== "failed").length;
            const rightActive = Object.values(connectionStates.right).filter(s => s !== "completed" && s !== "failed").length;
            
            if (leftActive > 0 || rightActive > 0) {
                statusEl.innerHTML = `Connection Status: Left (${leftActive} active) | Right (${rightActive} active)`;
            } else {
                statusEl.innerHTML = `Connection Status: Waiting for connections...`;
            }
        }

        function handleAutoRelay(message, sourceSide) {
    try {
        // Check if it's a JSON string
        let parsedMsg = typeof message === 'string' ? JSON.parse(message) : message;
        
        // Handle both packed and unpacked messages
        let innerMessage = parsedMsg;
        if (parsedMsg.m) {
            // This is a packed message from the queue
            innerMessage = JSON.parse(parsedMsg.m);
        }
        
        // Check if it's a basic command type with request property
        if (innerMessage.type === "B" && innerMessage.req) {
            const command = innerMessage.req;
            
            // Handle specifically joinroom and play commands
            if (command === "joinroom" || command === "play") {
                updateDebugOutput(`Auto-relaying '${command}' command from ${sourceSide}`);
                
                // Create the original format message for both sides
                const relayMessage = JSON.stringify({
                    request: command,
                    streamID: "remote",
                    from: "remote",
                    roomid: "default"
                });
                
               if (iframes.left && iframes.left.contentWindow ) {
					iframes.left.contentWindow.postMessage({"function":"routeMessage", "value": relayMessage}, '*');
					updateDebugOutput(`Auto-relayed '${command}' to left iframe`);
				}
                
                // Return true to indicate we handled it
                return true;
            }
        }
        
        // Not a command we auto-relay
        return false;
    } catch (e) {
        updateDebugOutput(`Error in auto-relay: ${e.message}`);
        return false;
    }
}

// Update the manualSend function to check for auto-relay commands
function manualSend(message, side) {
    try {
        // Check if this is a duplicate message
        if (messageQueues[side].length > 0) {
            try {
                const lastMsg = JSON.parse(messageQueues[side][messageQueues[side].length - 1]);
                const newMsg = JSON.parse(message);
                
                // Compare stringified versions to detect duplicates
                if (JSON.stringify(lastMsg.m) === JSON.stringify(newMsg)) {
                    return;
                }
            } catch (e) {
                // Continue if parsing fails
            }
        }
        
        // Extract UUID from original message for tracking
        let uuid = null;
        try {
            const parsedMsg = JSON.parse(message);
            uuid = parsedMsg.UUID;
            
            if (uuid) {
                // Update connection state
                if (!connectionStates[side][uuid]) {
                    connectionStates[side][uuid] = "initializing";
                    updateConnectionStatus();
                }
                
                // If this is a new connection and we're not processing one already
                if (!currentConnectionIds[side] && uuid) {
                    currentConnectionIds[side] = uuid;
                    updateConnectionStatus();
                    
                    // Set timeout for connection
                    if (processingTimeouts[side]) clearTimeout(processingTimeouts[side]);
                    processingTimeouts[side] = setTimeout(() => {
                        if (currentConnectionIds[side] === uuid) {
                            connectionStates[side][currentConnectionIds[side]] = "failed";
                            currentConnectionIds[side] = null;
                            updateConnectionStatus();
                        }
                    }, 60000); // 60 seconds timeout
                }
            }
        } catch (e) {
            updateDebugOutput(`Error parsing message for UUID (${side}): ${e.message}`);
        }
        
        let compressedMessage = message;
        let originalLength = message.length;
        let compressionRatio = "0%";
        
        // Compress the message if enabled
        if (compressionEnabled) {
            try {
                const compressed = compressMessage(message);
				
				console.info(compressed);
                
                // Skip if compression returned null
                if (compressed === null) {
                    updateDebugOutput(`Compression returned null, skipping message (${side})`);
                    return;
                }
                
                compressedMessage = compressed;
                compressionRatio = ((compressedMessage.length / originalLength) * 100).toFixed(1) + "%";
                
                // Check if this is a command we should auto-relay
                try {
                    const parsedCompressed = JSON.parse(compressedMessage);
                    if (handleAutoRelay(parsedCompressed, side)) {
                        // We handled it with auto-relay, so no need to add to queue
                        updateDebugOutput(`Auto-relayed command from ${side}, not adding to queue`);
                        
                        // Update compression stats display anyway
                        document.getElementById("compressionStats").textContent = 
                            `Compression: ${originalLength} → ${compressedMessage.length} bytes (${compressionRatio})`;
                        
                        // Update status
                        const statusEl = document.getElementById("outputStatus");
                        statusEl.textContent = `${side.toUpperCase()}: Auto-relayed command - ${new Date().toLocaleTimeString()}`;
                        
                        return; // Exit early since we handled the command
                    }
                } catch (e) {
                    updateDebugOutput(`Error checking for auto-relay: ${e.message}`);
                }
            } catch (e) {
                updateDebugOutput(`Compression error: ${e.message}, using original message (${side})`);
                compressedMessage = message;
                compressionRatio = "100%";
            }
        } else {
            compressionRatio = "100% (disabled)";
            
            // Even if compression is disabled, check if we can auto-relay
            try {
                if (handleAutoRelay(message, side)) {
                    // We handled it with auto-relay, so no need to add to queue
                    updateDebugOutput(`Auto-relayed command from ${side}, not adding to queue (compression disabled)`);
                    
                    // Update compression stats display anyway
                    document.getElementById("compressionStats").textContent = 
                        `Compression: ${originalLength} bytes (disabled)`;
                    
                    // Update status
                    const statusEl = document.getElementById("outputStatus");
                    statusEl.textContent = `${side.toUpperCase()}: Auto-relayed command - ${new Date().toLocaleTimeString()}`;
                    
                    return; // Exit early since we handled the command
                }
            } catch (e) {
                updateDebugOutput(`Error checking for auto-relay: ${e.message}`);
            }
        }
        
        // Update compression stats display
        document.getElementById("compressionStats").textContent = 
            `Compression: ${originalLength} → ${compressedMessage.length} bytes (${compressionRatio})`;
        
        const msg = {m: compressedMessage, t: Date.now()};
        const packed = JSON.stringify(msg);
        
        // Add to message queue
        messageQueues[side].push(packed);
        
        // Add to buffer for auto-transfer if enabled
        if (autoTransferEnabled) {
            // Store in the buffer
            messageBuffers[side].push(packed);
            
            // Determine the target side
            const targetSide = side === 'left' ? 'right' : 'left';
            
            // Use setTimeout to ensure messages are processed in the correct order
            setTimeout(() => {
                try {
                    transferMessage(targetSide, packed);
                } catch (err) {
                    updateDebugOutput(`Error in auto-transfer: ${err.message}`);
                }
            }, 100);
        }
        
        // Update status
        const statusEl = document.getElementById("outputStatus");
        statusEl.textContent = `${side.toUpperCase()}: Message added to queue (${messageQueues[side].length} pending) - ${new Date().toLocaleTimeString()}`;
        
        // Update the output area if it's currently empty
        const outputArea = document.getElementById("outputArea");
        if (outputArea.value === "") {
            outputArea.value = `[${side.toUpperCase()}] ${packed}`;
        }
    } catch (e) {
        updateDebugOutput(`Error in manualSend (${side}): ${e.message}`);
    }
}

        function processIncomingMessage(input, targetSide) {
            const statusEl = document.getElementById("outputStatus");
            
            try {
                const msg = JSON.parse(input);
                if (!msg.t || !msg.m) {
                    statusEl.textContent = "Error: Invalid message format";
                    updateDebugOutput("Invalid message format: missing t or m property");
                    return;
                }
                
                // Don't be too strict about message age
                if (Date.now() - msg.t > 1200000) { // 20 minutes
                    updateDebugOutput("Message is old (>20 minutes) but processing anyway");
                }
                
                let decompressedMessage = msg.m;
                let skipMessage = false;
                
                // Try to decompress if needed and enabled
                if (compressionEnabled) {
                    try {
                        // First check if it's our compressed format
                        let parsedIncoming;
                        try {
                            parsedIncoming = JSON.parse(msg.m);
                            
                            // Look for our signature
                            if (parsedIncoming.type && ['O', 'C', 'B'].includes(parsedIncoming.type)) {
                              //  updateDebugOutput(`Detected compressed message type: ${parsedIncoming.type}`);
                                
                                // Skip empty candidate sets completely
                                if (parsedIncoming.type === 'C' && (!parsedIncoming.cand || parsedIncoming.cand === '')) {
                                    updateDebugOutput("Skipping empty candidate set");
                                    skipMessage = true;
                                    statusEl.textContent = "Skipped empty candidate message - " + new Date().toLocaleTimeString();
                                    return;
                                }
                                
                                // Decompress
                                let decompressed = decompressMessage(msg.m);
                                
                                // Skip if decompression returned null 
                                if (decompressed === null) {
                                    updateDebugOutput("Skipping null decompressed message");
                                    skipMessage = true;
                                    statusEl.textContent = "Skipped empty message - " + new Date().toLocaleTimeString();
                                    return;
                                }
                                
                                // Modify the UUID based on target side before sending
                                try {
                                    let parsedDecompressed = JSON.parse(decompressed);
                                    if (parsedDecompressed.UUID) {
                                        parsedDecompressed.UUID = targetSide === 'left' ? myUUID.left : myUUID.right;
                                        decompressed = JSON.stringify(parsedDecompressed);
                                    }
                                } catch (uuidErr) {
                                    updateDebugOutput(`Error modifying UUID: ${uuidErr.message}`);
                                }
                                
                                decompressedMessage = decompressed;
                             //   updateDebugOutput(`Successfully decompressed message: ${decompressedMessage.substring(0, 50)}...`);
                            }
                        } catch (e) {
                            // Not our compressed format or not JSON
                            updateDebugOutput(`Message doesn't appear to be compressed: ${e.message}`);
                        }
                    } catch (e) {
                        updateDebugOutput(`Error checking compression format: ${e.message}`);
                    }
                }
                
                if (!skipMessage && iframes[targetSide] && iframes[targetSide].contentWindow) {
                    // Send the message to the target iframe
                    updateDebugOutput(`Sending to ${targetSide} iframe: ${decompressedMessage}`);
                    iframes[targetSide].contentWindow.postMessage({"function":"routeMessage", "value":decompressedMessage}, '*');
                    statusEl.textContent = `Message processed successfully to ${targetSide} - ${new Date().toLocaleTimeString()}`;
                } else if (!iframes[targetSide] || !iframes[targetSide].contentWindow) {
                    updateDebugOutput(`Target iframe ${targetSide} not ready yet`);
                    statusEl.textContent = `Error: Target iframe ${targetSide} not ready - ${new Date().toLocaleTimeString()}`;
                }
            } catch(e) {
                statusEl.textContent = "Error: " + e.message;
                console.error("Failed to process message:", e);
                updateDebugOutput(`Error processing message: ${e.message}`);
            }
        }
        
        function transferMessage(targetSide, message) {
			try {
				// Check if this is a message we should auto-relay
				try {
					const msgObj = JSON.parse(message);
					const sourceSide = targetSide === 'left' ? 'right' : 'left';
					
					if (handleAutoRelay(msgObj, sourceSide)) {
						// We handled it with auto-relay, so just remove from queue
						if (sourceSide === 'left' && messageQueues.left.length > 0) {
							messageQueues.left.shift();
						} else if (sourceSide === 'right' && messageQueues.right.length > 0) {
							messageQueues.right.shift();
						}
						
						// Update display
						const outputArea = document.getElementById("outputArea");
						outputArea.value = "";
						
						return; // Exit early since we handled the command
					}
				} catch (e) {
					updateDebugOutput(`Error checking for auto-relay during transfer: ${e.message}`);
				}
				
				// Otherwise, process normally
				processIncomingMessage(message, targetSide);
				
				// Remove from the queue after successful transfer
				if (targetSide === 'right' && messageQueues.left.length > 0) {
					messageQueues.left.shift();
				} else if (targetSide === 'left' && messageQueues.right.length > 0) {
					messageQueues.right.shift();
				}
				
				// Update display
				const outputArea = document.getElementById("outputArea");
				outputArea.value = "";
			} catch (e) {
				updateDebugOutput(`Error transferring message: ${e.message}`);
			}
		}
        
        // Function to toggle auto transfer
        function toggleAutoTransfer() {
            autoTransferEnabled = !autoTransferEnabled;
            
            const btn = document.getElementById("autoTransfer");
            if (autoTransferEnabled) {
                btn.textContent = "Disable Auto Transfer";
                btn.classList.add("auto-transfer-active");
             //   updateDebugOutput("Auto transfer enabled");
            } else {
                btn.textContent = "Enable Auto Transfer";
                btn.classList.remove("auto-transfer-active");
           //     updateDebugOutput("Auto transfer disabled");
            }
        }

        // Initialize the page once DOM is loaded
        document.addEventListener('DOMContentLoaded', function() {
            // Create and add left iframe
            iframes.left = document.createElement("iframe");
            iframes.left.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
            iframes.left.src = "../?bypass&waitice&password=false&room=default&push=local&s&debug";
            document.getElementById("iframe-left").appendChild(iframes.left);
            
            // Create and add right iframe with a slight delay to prevent initialization conflicts
            setTimeout(() => {
                iframes.right = document.createElement("iframe");
                iframes.right.allow = "autoplay;camera;microphone;fullscreen;picture-in-picture;display-capture;";
                iframes.right.src = "../?bypass&waitice&password=false&room=default&push=local&solo";
                document.getElementById("iframe-right").appendChild(iframes.right);
                
                updateDebugOutput("Both iframes initialized");
            }, 500);
            
            // Setup debug toggle
            document.getElementById("debugToggle").addEventListener("click", function() {
                debugMode = !debugMode;
                document.getElementById("debugOutput").style.display = debugMode ? "block" : "none";
            });
            
            // Setup compression toggle
            document.getElementById("enableCompression").addEventListener("change", function() {
                compressionEnabled = this.checked;
                
                // Clear any previous messageQueue when changing compression mode
                // to avoid mixing compressed and uncompressed messages
                if (messageQueues.left.length > 0 || messageQueues.right.length > 0) {
                    if (confirm("Changing compression mode will clear the current message queues. Continue?")) {
                        messageQueues.left = [];
                        messageQueues.right = [];
                        document.getElementById("outputArea").value = "";
                        document.getElementById("outputStatus").textContent = "Message queues cleared due to compression mode change";
                    } else {
                        // Revert the checkbox if user cancels
                        this.checked = compressionEnabled;
                        return;
                    }
                }
                
                compressionEnabled = this.checked;
               // updateDebugOutput(`Compression ${compressionEnabled ? 'enabled' : 'disabled'}`);
            });
            
            // Setup manual transfer buttons
            document.getElementById("leftToRight").addEventListener("click", function() {
                if (messageQueues.left.length > 0) {
                    const message = messageQueues.left[0];
                    transferMessage('right', message);
                 //   updateDebugOutput("Manually transferred message from left to right");
                    
                    // Update output area to show next message if available
                    if (messageQueues.left.length > 0) {
                        document.getElementById("outputArea").value = `[LEFT] ${messageQueues.left[0]}`;
                    } else {
                        document.getElementById("outputArea").value = "";
                    }
                } else {
                //    updateDebugOutput("No messages in left queue to transfer");
                }
            });
            
            document.getElementById("rightToLeft").addEventListener("click", function() {
                if (messageQueues.right.length > 0) {
                    const message = messageQueues.right[0];
                    transferMessage('left', message);
                //    updateDebugOutput("Manually transferred message from right to left");
                    
                    // Update output area to show next message if available
                    if (messageQueues.right.length > 0) {
                        document.getElementById("outputArea").value = `[RIGHT] ${messageQueues.right[0]}`;
                    } else {
                        document.getElementById("outputArea").value = "";
                    }
                } else {
               //     updateDebugOutput("No messages in right queue to transfer");
                }
            });
            
            // Setup auto transfer toggle
            document.getElementById("autoTransfer").addEventListener("click", toggleAutoTransfer);
            
            // Set up the message event handler for iframe communication
            window.addEventListener("message", function(e) {
                // Check if message is from one of our iframes
                if (e.source === iframes.left.contentWindow) {
                    if ("bypass" in e.data) {
                        manualSend(e.data.bypass, 'left');
                    }
                } else if (e.source === iframes.right.contentWindow) {
                    if ("bypass" in e.data) {
                        manualSend(e.data.bypass, 'right');
                    }
                }
            });
            
            // Enable debug mode by default for easier troubleshooting
            debugMode = true;
            document.getElementById("debugOutput").style.display = "block";
           // updateDebugOutput("Debug mode enabled by default");
          //  updateDebugOutput("Dual iframe setup with auto-transfer capability");
        });
    </script>
</body>
</html>